package edu.utexas.ece.pharos.demo.homeAutomation.logAnalyzer;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Enumeration;
import java.util.Vector;

import edu.utexas.ece.pharos.demo.homeAutomation.CPSPredicateRoomBright;
import edu.utexas.ece.pharos.demo.homeAutomation.CPSPredicateRoomDim;
import edu.utexas.ece.pharos.utils.FileLogger;
import edu.utexas.ece.pharos.utils.Stats;

/**
 * Analyze the log file generated by an experiment involving 
 * no cyber-physical assertions.
 * 
 * @author Chien-Liang Fok
 */
public class AnalyzeNoAssertionLog {
	
	/**
	 * The max amount of time that can pass between executing an actuation 
	 * command and observing its intended physical consequences.
	 */
	public static final long MAX_ACTUATION_LATENCY = 1000;
	
	Vector<Experiment> experiments = new Vector<Experiment>();
	
	/**
	 * The constructor.
	 * 
	 * @param fileName The name of the log file.
	 */
	public AnalyzeNoAssertionLog() {
		// Read the log files...
		FilenameFilter filter = new FilenameFilter() {
		    public boolean accept(File dir, String name) {
		        return name.startsWith("LightSwitchApp") && name.contains(".log");
		    }
		};
		
		File dir = new File(".");

		String[] logFiles = dir.list(filter);
		if (logFiles == null) {
		    System.err.println("No files found.");
		    System.exit(1);
		} else {
		    for (int i=0; i<logFiles.length; i++) {
		    	String expFileName = "./" + logFiles[i];
		    	System.out.println("Reading log " + expFileName);
		        readFile(expFileName);
		    }
		}
		
		int totalNumRounds = 0;
		Enumeration<Experiment> e = experiments.elements();
		while (e.hasMoreElements()) {
			totalNumRounds += e.nextElement().rounds.size();
		}
		System.out.println("Number of rounds: " + totalNumRounds);
		
		//printRound2();
		analyzeLoff();
		analyzeLon();
		analyzeCmdExecLatency();
		analyzeTotalExecTime();
	}
	
	private void analyzeTotalExecTime() {
		Vector<Double> measurements = new Vector<Double>();
		
		Enumeration<Experiment> ee = experiments.elements();
		while (ee.hasMoreElements()) {
			Experiment exp = ee.nextElement();
			Enumeration<Round> e = exp.rounds.elements();
			while (e.hasMoreElements()) {
				Round currRound = e.nextElement();
			
				measurements.add((double)(currRound.timeOfLightsOnCmd - currRound.timeOfLightsOffCmd));
			}
		}
		
		double avg = Stats.getAvg(measurements);
		double conf95 = Stats.getConf95(measurements);
		
		DecimalFormat df = new DecimalFormat("#.##");
		System.out.println("Execution time = " + df.format(avg) + " +- " + df.format(conf95)
				+ " (sample size = " + measurements.size() + ")");
	}
	
	private void analyzeCmdExecLatency() {
		Vector<Double> measurements = new Vector<Double>();
		
		Enumeration<Experiment> ee = experiments.elements();
		while (ee.hasMoreElements()) {
			Experiment exp = ee.nextElement();
			Enumeration<Round> e = exp.rounds.elements();
			while (e.hasMoreElements()) {
				Round currRound = e.nextElement();

				// divide by 1000 to convert to micro-seconds
				measurements.add(currRound.latencyLightsOffCmd / 1000.0);
				measurements.add(currRound.latencyLightsOnCmd / 1000.0);
			}
		}
		
		double avg = Stats.getAvg(measurements);
		double conf95 = Stats.getConf95(measurements);
		
		DecimalFormat df = new DecimalFormat("#.##");
		System.out.println("Cmd latency = " + df.format(avg) + " +- " + df.format(conf95)
				+ " (sample size = " + measurements.size() + ")");
	}
	
	private void analyzeLon() {
		
		Vector<Double> measurements = new Vector<Double>();
		
		Enumeration<Experiment> ee = experiments.elements();
		while (ee.hasMoreElements()) {
			Experiment exp = ee.nextElement();
			Enumeration<Round> e = exp.rounds.elements();
			while (e.hasMoreElements()) {
				Round currRound = e.nextElement();

				long lightsOnTime = currRound.timeOfLightsOnCmd;
				LightReading transitionReading = null;

				// Search for the first light measurement after the lights off time that is low
				for (int i=0; i < exp.lightReadings.size() && transitionReading == null; i++) {
					LightReading currReading = exp.lightReadings.get(i);
					if (currReading.timestamp > lightsOnTime && currReading.timestamp < lightsOnTime + MAX_ACTUATION_LATENCY 
							&& currReading.value > CPSPredicateRoomBright.BRIGHT_THRESHOLD) 
					{
						transitionReading = currReading;
					}
				}

				if (transitionReading != null) {
					long deltaTime = transitionReading.timestamp - lightsOnTime;
					measurements.add((double)deltaTime);
				} else {
					System.err.println("Unable to find transition reading for round " + currRound.roundNumber + "!");
					System.exit(1);
				}
			}
		}
		
		double avg = Stats.getAvg(measurements);
		double conf95 = Stats.getConf95(measurements);
		
		DecimalFormat df = new DecimalFormat("#.##");
		System.out.println("L_on = " + df.format(avg) + " +- " + df.format(conf95)
				+ " (sample size = " + measurements.size() + ")");
		
	}
	
	private void analyzeLoff() {
		
		Vector<Double> measurements = new Vector<Double>();
		
		Enumeration<Experiment> ee = experiments.elements();
		while (ee.hasMoreElements()) {
			Experiment exp = ee.nextElement();
			Enumeration<Round> e = exp.rounds.elements();
			while (e.hasMoreElements()) {
				Round currRound = e.nextElement();

				long lightsOffTime = currRound.timeOfLightsOffCmd;
				LightReading transitionReading = null;

				// Search for the first light measurement after the lights off time that is low
				for (int i=0; i < exp.lightReadings.size() && transitionReading == null; i++) {
					LightReading currReading = exp.lightReadings.get(i);
					if (currReading.timestamp > lightsOffTime && currReading.timestamp < lightsOffTime + MAX_ACTUATION_LATENCY 
							&& currReading.value < CPSPredicateRoomDim.DIM_THRESHOLD) 
					{
						transitionReading = currReading;
					}
				}

				if (transitionReading != null) {
					long deltaTime = transitionReading.timestamp - lightsOffTime;
					measurements.add((double)deltaTime);
				} else {
					System.err.println("Unable to find transition reading for round " + currRound.roundNumber + "!");
					System.exit(1);
				}
			}
		}
		
		double avg = Stats.getAvg(measurements);
		double conf95 = Stats.getConf95(measurements);
		
		DecimalFormat df = new DecimalFormat("#.##");
		System.out.println("L_off = " + df.format(avg) + " +- " + df.format(conf95)
				+ " (sample size = " + measurements.size() + ")");
		
	}
	
	private void printRound2() {
		Experiment exp = experiments.get(2);
		Round round1 = exp.rounds.get(1);
		long timeOfLightsOffCmd = round1.timeOfLightsOffCmd;
		long timeOfLightsOnCmd = round1.timeOfLightsOnCmd;
		
		// Gather sensor readings one second before and after the
		// experiment.
		long startTime = timeOfLightsOffCmd - 1000;
		long endTime = timeOfLightsOnCmd + 1000;
		
		Vector<LightReading> relevantReadings = new Vector<LightReading>();
		
		Enumeration<LightReading> e = exp.lightReadings.elements();
		while (e.hasMoreElements()) {
			LightReading currReading = e.nextElement();
			if (currReading.timestamp > startTime && currReading.timestamp < endTime)
				relevantReadings.add(currReading);
		}
		
		FileLogger flogger = new FileLogger("Round2.txt", false);
		flogger.log("Time of lights off command = " + (timeOfLightsOffCmd - startTime));
		flogger.log("Time of lights on command = " + (timeOfLightsOnCmd - startTime));
		
		flogger.log("Timestamp (ms), Timestamp (s), Sequence Number, Light Reading");
		e = relevantReadings.elements();
		while (e.hasMoreElements()) {
			LightReading currReading = e.nextElement();
			long timestamp = currReading.timestamp - startTime;
			flogger.log(timestamp + ", " + (timestamp / 1000.0) + ", " + currReading.seqno + ", " + currReading.value);
		}
		
	}
	
	/**
	 * Reads and organizes the data contained in the log file.
	 * 
	 * @param fileName The log file name.
	 */
	private void readFile(String fileName) {
		File file = new File(fileName);
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			System.exit(1);
		}
		String line = null;
		
		Experiment exp = new Experiment();

		try {
			while ((line = br.readLine()) != null){

				// Save the light measurements
				if (line.contains("Received AmbientLight message")) {
					String[] tokens = line.split("[\\[\\]]");
					long timestamp = Long.valueOf(tokens[1]);
					line = br.readLine();
					line = br.readLine();
					line = br.readLine();
					tokens = line.split("\\s");
					int seqno = Integer.valueOf(tokens[4]);
					line = br.readLine();
					line = br.readLine();
					line = br.readLine();
					line = br.readLine();
					tokens = line.split("\\s");
					int lightLevel = Integer.valueOf(tokens[4]);
					exp.lightReadings.add(new LightReading(timestamp, seqno, lightLevel));
				}
				else if (line.contains("Round")) {
					String[] tokens = line.split("[\\s]");
					int roundNumber = Integer.valueOf(tokens[4]);
					
					line = br.readLine();
					tokens = line.split("[\\[\\]]");
					long timeOfLightsOffCmd = Long.valueOf(tokens[1]);
					
					line = br.readLine();
					tokens = line.split("[\\s]");
					long latencyLightsOffCmd = Long.valueOf(tokens[8]);
					
					line = br.readLine();
					
					line = br.readLine();
					tokens = line.split("[\\[\\]]");
					long timeOfLightsOnCmd = Long.valueOf(tokens[1]);
					
					line = br.readLine();
					tokens = line.split("[\\s]");
					long latencyLightsOnCmd = Long.valueOf(tokens[8]);
					
					Round round = new Round(roundNumber, timeOfLightsOffCmd, 
							latencyLightsOffCmd, timeOfLightsOnCmd, latencyLightsOnCmd);
					exp.rounds.add(round);
				}
			}
		} catch (NumberFormatException e) {
			e.printStackTrace();
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}

		// debugging code to verify that the light readings were successfully read in.
//		Enumeration<LightReading> e = exp.lightReadings.elements();
//		while (e.hasMoreElements()) {
//			System.out.println(e.nextElement().toString());
//		}
		
		// debugging code to verify that the round information was successfully read in.
//		Enumeration<Round> e2 = exp.rounds.elements();
//		while (e2.hasMoreElements()) {
//			System.out.println(e2.nextElement().toString());
//		}
		
		experiments.add(exp);
	}
	
	class Experiment {
		Vector<LightReading> lightReadings = new Vector<LightReading>();
		Vector<Round> rounds = new Vector<Round>();
		
		public Experiment() {
		}
	}
	
	class Round {
		int roundNumber;
		long timeOfLightsOffCmd, latencyLightsOffCmd,
		timeOfLightsOnCmd, latencyLightsOnCmd;
		
		public Round(int roundNumber, long timeOfLightsOffCmd, long latencyLightsOffCmd,
				long timeOfLightsOnCmd, long latencyLightsOnCmd) 
		{
			this.roundNumber = roundNumber;
			this.timeOfLightsOffCmd = timeOfLightsOffCmd;
			this.latencyLightsOffCmd = latencyLightsOffCmd;
			this.timeOfLightsOnCmd = timeOfLightsOnCmd;
			this.latencyLightsOnCmd = latencyLightsOnCmd;
		}
		
		public String toString() {
			return roundNumber + ", " + timeOfLightsOffCmd + ", " + latencyLightsOffCmd 
				+ ", " + timeOfLightsOnCmd + ", " + latencyLightsOnCmd;
		}
	}
	
	class LightReading {
		long timestamp;
		int seqno;
		int value;
		
		public LightReading(long timestamp, int seqno, int value) {
			this.timestamp = timestamp;
			this.seqno = seqno;
			this.value = value;
		}
		
		public String toString() {
			return timestamp + ", " + seqno + ", " + value;
		}
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new AnalyzeNoAssertionLog();
	}

}
